// Created by Roman Novitsky on 22.04.2009


#region Imported Namespaces


using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;


#endregion


namespace WtfManager.UIManager
{
  public class SelectionManager: DependencyObject
  {
    #region Fields


    public static readonly DependencyProperty SelectionManagerProperty =
      DependencyProperty.RegisterAttached("SelectionManager", typeof(SelectionManager), typeof(SelectionManager),
                                          new PropertyMetadata(SelectionManagerChanged));

    private ItemsControl _itemsControl;
    private DependencyPropertyDescriptor _itemsSourcePropertyDescriptor;
    private ListBoxItem _itemToSelectOnMouseButtonUp;
    private bool _mouseMoveDetected;


    #endregion


    #region Properties/Indexers


    protected IEnumerable CurrentItemsSource
    {
      get;
      private set;
    }


    protected ItemsControl ItemsControl
    {
      get
      {
        return _itemsControl;
      }
      private set
      {
        if (_itemsControl != null && value != null)
        {
          throw new InvalidOperationException("SelectionManager already has an items control.");
        }

        if (_itemsControl != null)
        {
          _itemsSourcePropertyDescriptor.RemoveValueChanged(_itemsControl, ItemsSourceChanged);
          _itemsSourcePropertyDescriptor = null;

          PreviousItemsSource = null;
          CurrentItemsSource = null;
          _itemsControl.ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged;

          if (_itemsControl is Selector)
          {
            var selector = (Selector)_itemsControl;
            selector.SelectionChanged -= SelectorSelectionChanged;
          }

          if (_itemsControl is TreeView)
          {
            var treeView = (TreeView)_itemsControl;
            treeView.SelectedItemChanged -= TreeViewSelectedItemChanged;
          }
        }

        _itemsControl = value;

        if (_itemsControl == null)
        {
          return;
        }

        CurrentItemsSource = _itemsControl.ItemsSource;

        _itemsSourcePropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty,
                                                                                   typeof(ItemsControl));
        _itemsSourcePropertyDescriptor.AddValueChanged(_itemsControl, ItemsSourceChanged);

        _itemsControl.ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged;

        if(_itemsControl is ListBox)
        {
          _itemsControl.PreviewMouseDown += ItemsControlPreviewMouseDown;
        }

        if (_itemsControl is Selector)
        {
          var selector = (Selector)_itemsControl;
          selector.SelectionChanged += SelectorSelectionChanged;
        }

        if (_itemsControl is TreeView)
        {
          var treeView = (TreeView)_itemsControl;
          treeView.SelectedItemChanged += TreeViewSelectedItemChanged;
        }
      }
    }


    private void ItemsControlPreviewMouseUp(object sender, MouseButtonEventArgs e)
    {
      var listBox = (ListBox)_itemsControl;

      if (!_mouseMoveDetected)
      {
        listBox.SelectedItems.Clear();
      }

      _itemToSelectOnMouseButtonUp.IsSelected = true;

      _itemsControl.PreviewMouseUp -= ItemsControlPreviewMouseUp;
      _itemsControl.MouseMove -= ItemsControlMouseMove;
    }


    void ItemsControlPreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
      var listBox = (ListBox)sender;

      if(listBox.SelectedItems.Count == 1)
      {
        return;
      }

      if(Keyboard.Modifiers == ModifierKeys.Control || Keyboard.Modifiers == ModifierKeys.Shift)
      {
        return;
      }

      var originalSource = e.OriginalSource as DependencyObject;
      
      while(originalSource != null)
      {
        if(originalSource is ListBoxItem)
        {
          _itemToSelectOnMouseButtonUp = (ListBoxItem)originalSource;
          _itemsControl.PreviewMouseUp += ItemsControlPreviewMouseUp;
          _itemsControl.MouseMove += ItemsControlMouseMove;

          _mouseMoveDetected = false;

          e.Handled = true;

          return;
        }

        originalSource = VisualTreeHelper.GetParent(originalSource);
      }
    }


    private void ItemsControlMouseMove(object sender, MouseEventArgs e)
    {
      _mouseMoveDetected = true;
    }


    protected IEnumerable PreviousItemsSource
    {
      get;
      private set;
    }


    protected object SelectedValue
    {
      get
      {
        if (_itemsControl is Selector)
        {
          var treeView = (Selector)_itemsControl;
          return treeView.SelectedValue;
        }

        if (_itemsControl is TreeView)
        {
          var treeView = (TreeView)_itemsControl;
          return treeView.SelectedValue;
        }

        return null;
      }
      private set
      {
        if (_itemsControl is Selector)
        {
          var selector = (Selector)_itemsControl;
          selector.SelectedValue = value;
        }

        if (_itemsControl is TreeView)
        {
          var treeViewItem = _itemsControl.ItemContainerGenerator.ContainerFromItem(value) as TreeViewItem;

          if (treeViewItem != null)
          {
            treeViewItem.IsSelected = true;
          }
        }
      }
    }


    #endregion


    #region Methods


    private void TreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
      OnSelectionChanged();
    }


    private void SelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
      OnSelectionChanged();
    }


    protected virtual void OnSelectionChanged()
    {
    }


    private void ItemsSourceChanged(object sender, EventArgs e)
    {
      PreviousItemsSource = CurrentItemsSource;
      CurrentItemsSource = _itemsControl.ItemsSource;

      Debug.Assert(_itemsControl.ItemsSource == CurrentItemsSource);

      OnItemsSourceChanged();
    }


    private void ItemContainerGeneratorStatusChanged(object sender, EventArgs e)
    {
      if (_itemsControl.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
      {
        return;
      }

      if(_itemsControl.ItemsSource == CurrentItemsSource)
        OnItemsSourceChanged();
    }


    protected virtual void OnItemsSourceChanged()
    {
      if (_itemsControl.Items != null && _itemsControl.Items.Count > 0 && SelectedValue == null)
      {
        object selectedValue = _itemsControl.Items[0];

        SelectItem(selectedValue, true);
      }
    }


    protected void SelectItem(object selectedValue, bool focusOnItem)
    {
      SelectedValue = selectedValue;

      var item = _itemsControl.ItemContainerGenerator.ContainerFromItem(selectedValue) as FrameworkElement;

      if (item != null && focusOnItem)
      {
        if(_itemsControl.IsFocused)
          Keyboard.Focus(item);

        item.BringIntoView();
      }
    }


    private static void SelectionManagerChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
      var itemsControl = (ItemsControl)dependencyObject;

      var oldSelectionManager = e.OldValue as SelectionManager;

      if (oldSelectionManager != null)
      {
        oldSelectionManager.ItemsControl = null;
      }

      var newSelectionManager = e.NewValue as SelectionManager;

      if (newSelectionManager != null)
      {
        newSelectionManager.ItemsControl = itemsControl;
      }
    }


    public static void SetSelectionManager(DependencyObject element, SelectionManager value)
    {
      if (element == null)
      {
        throw new ArgumentNullException("element");
      }

      element.SetValue(SelectionManagerProperty, value);
    }


    public static SelectionManager GetSelectionManager(DependencyObject element)
    {
      if (element == null)
      {
        throw new ArgumentNullException("element");
      }
      return (SelectionManager)element.GetValue(SelectionManagerProperty);
    }


    #endregion
  }
}